FIT2-2022b 第07回 ミニプロサンプル解説 + 実習
今日以後原則出席を取るのを辞めます!
12/21(水)を中間発表日に据えます
- 参加が成績に含まれるため、必ず出席するように
- 先ずはこのタイミングを目指してミニプロ制作をしっかり進めましょう
前回授業振り返り
オブジェクト、メソッド
オブジェクト:コード内に登場する概念を「class」にまとめて可読性/保守性を高める
code:python
# ボールという概念(class)を定義します
class Ball:
# 個々のボールに関係なくボールの共通概念として速度があります (Ball.speedでどこからも参照可能)
speed = 1
# ボール新しく作る時は最初に必ずこの処理を実行してください (initializeの略)
def __init__(self):
self.reset() # ボールの内部定義にあるresetを実行します
# ボールはreset()することができます
def reset(self):
# reset()するとは、こういうことです
self.x = pyxel.rndi(0, 199) #self.変数名 と書くと値は保存される self.y = 0
angle = pyxel.rndi(30,150) # angleはself.がないので処理終了後破棄される
self.vx = pyxel.cos(angle)
self.vy = pyxel.sin(angle)
# ボールはmove()することができます
def move(self):
# move()するとは、こういうことです
self.x = self.x + self.vx * Ball.speed
self.y = self.y + self.vy * Ball.speed
# i番目のボールが左右の壁に当たったら、x軸の移動方向を反転
if self.x <= 0 or 200 <= self.x:
self.vx = self.vx * -1
# 新規作成は Ball()、作られたものをインスタンスと呼ぶ
# Ball()を作成すると __init__が実行され、self.reset()が実行されるのでx,y,vx,vyがセットされる
for b in balls: # オブジェクトのリストはこんなふうにfor文で回せるから便利
b.move() # 個別のボールをmove()する
if b.y > 200: # 個別のボールのy座標が200より大きければ…
b.reset() # 個別のボールをリセットする
読みやすい!
余談1: self.reset()とb.reset()
class Ball: の中でボール自身の処理を呼び出すときは、自分目線なので self.xxx()
class Ball: の外 でボールを操作するときは、他人目線なのでボールの変数名.xxx()
メソッド:オブジェクトに処理をもたせる
前項で既に触れているので、値の渡し方(引数)の話を追加
code:python
class Pad:
def __init__(self, x, y): # こうすれば初期化時にも値を渡せる
def catch(self, ball):
return True
return False
def draw(self):
pyxel.rect(self.x - 20, self.y, 40, 10, 0)
ball = Ball()
if pad.catch(ball):
# スコアを増やす
pad.draw()
応用余談:クラス変数の挙動について
クラス変数 (Ball.speed、後述コードだとgravity)は同じクラスから生成された全てのインスタンスに影響すると言った
実は個別上書きも可能、そのときは self.クラス変数名で呼び出す
code:python
class Planet:
gravity = 1
def __init__(self, name):
self.name = name
def printClassGravity(self):
print(Planet.gravity)
def printInstanceGravity(self):
print(self.gravity)
earth = Planet('earth')
mars = Planet('mars')
print('before')
earth.printInstanceGravity()# 1
earth.printClassGravity() # 1
mars.printInstanceGravity() # 1
mars.printClassGravity() # 1
mars.gravity = 0.4 # 火星の重力は地球の約4割です
Planet.gravity = 2 # といいつつ惑星全ての重力を2倍にします
print('after')
earth.printInstanceGravity() # 2 ← Planet.gravityがしっかり反映されている
earth.printClassGravity() # 2
mars.printInstanceGravity() # 0.4 ← 個別に上書きされた状態を保持している!
mars.printClassGravity() # 2
print(Planet.gravity) # 2
今後の授業運営
今後の授業は「とにかくミニプロ開発」です!
次週以降採点要件に含まれる「基本設計書」「詳細設計書」も少しずつ紹介します
今回講師はあまりヘルプには回らず、今回はアンケート回答が多かった5種類の骨組みを多い順に解説します
https://gyazo.com/9a9f3634457be9b69402a9063222c3bb
「自分が作ろうと思っているゲームに近いもの」があればその解説にのみfocusして聞いてくれれば◎
関心がないテーマについては各自ワークを
基本は限られたリソース(1人のSA)を奪い合ってください
全てサンプルコードも配布します!
ただし「サンプルコードの直接流用」箇所は採点対象から除外されるのでご注意を
変数名の変更、程度ではNG (講師の独断と偏見により判断されます)
極力サンプルは最小限に留めます
サンプルを見ると思考が引っ張られる…!という心配があるひとは自力での実装で進めてください
全力でアドリブライブコーディングです!
共通の枠組み
code:python
import pyxel
# ここにゲームで使うクラスを定義していく
class Something:
def __init__(self):
self.x = 0
self.y = 0
def draw(self):
pyxel.rect(self.x, self.y, 10, 10, 0)
# ゲーム自体をclassとして定義
class App:
def __init__(self):
pyxel.init(200, 200)
self.something = Something()
pyxel.run(self.update, self.draw) #こうすれば Appクラスのupdate, drawをpyxel.runに渡せる def update(self):
pass # 何もしないという意味
def draw(self):
pyxel.cls(7)
self.something.draw()
App() # Appを初期化すると __init__が実行される
横スクロールアクション (仮)10:00~
キャラクターをキーボードの左右矢印で移動+スペースでジャンプができる
ジャンプ後自由落下して地面に落ちてくる
オブジェクトが流れてきて接触すると何かが起こる
code:python
import pyxel
class Ground:
def __init__(self):
self.height = 20
self.y = 180
def draw(self):
pyxel.rect(0, self.y, 200, self.height, 9)
class Player:
def __init__(self):
self.x = 20
self.y = 160 # groundの上辺180から自分の高さ10を引いたもの
self.height = 20
self.width = 10
self.vy = 0
def move(self):
if pyxel.btn(pyxel.KEY_RIGHT):
# 右矢印が押されている
self.x = self.x + 5
if pyxel.btn(pyxel.KEY_LEFT):
# 左矢印が押されている
self.x = self.x - 5
if pyxel.btnp(pyxel.KEY_SPACE):
# スペースが押されたらジャンプ
self.vy = -20
# vyは毎フレーム+4増えるが、どれだけ大きくても8で止まるように
self.vy = min(self.vy + 4, 8)
# キャラクターが地面にめり込まないように
self.y = min(self.y + self.vy, 160)
def draw(self):
pyxel.rect(self.x, self.y, self.width, self.height, 15)
# ゲーム自体をclassとして定義
class App:
def __init__(self):
pyxel.init(200, 200)
self.ground = Ground()
self.player = Player()
pyxel.run(self.update, self.draw) #こうすれば Appクラスのupdate, drawをpyxel.runに渡せる def update(self):
self.player.move()
def draw(self):
pyxel.cls(7)
self.ground.draw()
self.player.draw()
pyxel.text(0, 0, str(self.player.vy), 0)
App() # Appを初期化すると __init__が実行される
シューティングゲーム (仮)10:30~
敵がでてきて弾を打つ
自分もキーボードの左右で移動できて弾を打つ
お互い当たるとHPが減っていく
code:python
import pyxel
# ここにゲームで使うクラスを定義していく
class Bullet:
def __init__(self, x, y):
# 弾は生成時にx,y座標を受け取る
self.x = x
self.y = y
self.vy = -5
def move(self):
self.y = self.y + self.vy
def draw(self):
pyxel.circ(self.x, self.y, 1, 3)
class Player:
def __init__(self):
self.x = 100
self.y = 180
def move(self):
if pyxel.btn(pyxel.KEY_A):
# Aが押されていたら左に動く
self.x -= 5
if pyxel.btn(pyxel.KEY_D):
# Dが押されていたら右に動く
self.x += 5
def draw(self):
pyxel.circ(self.x, self.y, 10, 0)
# ゲーム自体をclassとして定義
class App:
def __init__(self):
pyxel.init(200, 200)
self.player = Player()
self.bullets = []
pyxel.run(self.update, self.draw) #こうすれば Appクラスのupdate, drawをpyxel.runに渡せる def update(self):
self.player.move()
if pyxel.btn(pyxel.KEY_SPACE):
# スペースキーが押されていたら、弾を新しく生成
# 弾のx,y座標はプレイヤーの現在位置とする
self.bullets.append(Bullet(self.player.x, self.player.y))
# 全ての弾を動かす
for bullet in self.bullets:
bullet.move()
def draw(self):
pyxel.cls(7)
self.player.draw()
for bullet in self.bullets:
bullet.draw()
App() # Appを初期化すると __init__が実行される
マップ内移動ゲーム (仮)11:00~
俯瞰型でマップ内を移動していく
キャラクターは上下左右で移動
壁は通り抜けられない
code:python
import pyxel
# ここにゲームで使うクラスを定義していく
class Map:
def __init__(self):
# マップ内の位置を管理するための表
# 0 = なにもなし、1 = 壁、2=プレイヤー
self.cells = [1, 1, 1, 1, 1, 1, 1, 1 ,1 ,1, 1, 0, 0, 0, 0, 0, 0, 0 ,0 ,1, 1, 0, 0, 0, 0, 0, 0, 0 ,0 ,1, 1, 0, 0, 1, 0, 0, 0, 0 ,0 ,1, 1, 0, 0, 1, 0, 0, 0, 0 ,0 ,1, 1, 0, 0, 1, 0, 0, 0, 0 ,0 ,1, 1, 0, 0, 1, 0, 0, 0, 0 ,0 ,1, 1, 0, 0, 0, 0, 0, 0, 0 ,0 ,1, 1, 0, 0, 0, 0, 0, 0, 0 ,0 ,1, 1, 1, 1, 1, 1, 1, 1, 1 ,1 ,1,] def draw(self):
# 画面上の描画位置を計算するために、何行目・何列目を処理しているか記録
currentRow = 0
currentColumn = 0
for row in self.cells:
# self.cellsから1行取り出す
currentRow += 1
for column in row:
# 取り出した行から列を取り出す
currentColumn += 1
if column == 1: # 壁だったら
# 壁の色で描画
pyxel.rect(currentColumn * 10, currentRow * 10, 10, 10, 6)
if column == 2: # プレイヤーだったら
# プレイヤーの色で描画
pyxel.rect(currentColumn * 10, currentRow * 10, 10, 10, 4)
currentColumn = 0
class Player:
def __init__(self, map):
# pyxelの座標系でなく、Map.cellsの座標系を管理
self.x = 1
self.y = 1
# プレイヤーの位置情報を地図に上書き
def move(self, map):
# 一旦プレイヤーが今いる場所を何もなしに塗りつぶす
if pyxel.btn(pyxel.KEY_W):
# 移動しようとする先が壁でない場合に限り移動できる
self.y -= 1
if pyxel.btn(pyxel.KEY_A):
self.x -= 1
if pyxel.btn(pyxel.KEY_S):
self.y += 1
if pyxel.btn(pyxel.KEY_D):
self.x += 1
# 移動処理完了後の場所にプレイヤーを改めて設置
# ゲーム自体をclassとして定義
class App:
def __init__(self):
pyxel.init(200, 200)
self.map = Map()
self.player = Player(self.map)
pyxel.run(self.update, self.draw) #こうすれば Appクラスのupdate, drawをpyxel.runに渡せる def update(self):
self.player.move(self.map)
def draw(self):
pyxel.cls(7)
self.map.draw()
App() # Appを初期化すると __init__が実行される
小技 11:00~
ゲーム開始画面を用意する
code:python
import pyxel
# ゲーム自体をclassとして定義
class App:
def __init__(self):
pyxel.init(200, 200)
self.mode = 'TITLE'
self.x = 0
pyxel.run(self.update, self.draw) #こうすれば Appクラスのupdate, drawをpyxel.runに渡せる def update(self):
if self.mode == 'TITLE':
if # ゲーム開始条件:
self.mode = 'PLAYING'
elif self.mode == 'PLAYING':
self.x += 1
if self.x > 200:
self.mode = 'TITLE'
self.x = 0
def draw(self):
if self.mode == 'TITLE':
pyxel.mouse(True)
pyxel.cls(7)
pyxel.text(50, 50, '!!!SHOOTING GAME!!!', 0)
elif self.mode == 'PLAYING':
pyxel.mouse(False)
# プレイ中の画面
pyxel.cls(7)
pyxel.circ(self.x, 100, 1, 4)
App() # Appを初期化すると __init__が実行される
クレーンゲーム (仮)11:30~
解説…すると作りきっちゃうかも
概念だけ
クレーンと景品があるべき
クレーンをキーボードで操作後、乱数で勝手に追加で動いたりするとそれっぽいよね
接触判定の作り込みがミソ?
code:python
import pyxel
class Prize:
def __init__(self):
self.x = 100
self.y = 180
def draw(self):
pyxel.rect(self.x, self.y, 20, 20 ,8)
class Crane:
def __init__(self):
self.x = 0
self.y = 0
self.alreadyUsedRight = False
self.alreadyUsedDown = False
def move(self):
if pyxel.btnr(pyxel.KEY_RIGHT):
self.alreadyUsedRight = True
if pyxel.btn(pyxel.KEY_RIGHT) and self.alreadyUsedRight == False:
self.x += 1
def draw(self):
pyxel.rect(self.x, self.y, 10, 10, 4)
if self.alreadyUsedRight == False:
pyxel.text(100, 100, "press right arrow!", 0)
# ゲーム自体をclassとして定義
class App:
def __init__(self):
pyxel.init(200, 200)
self.prize = Prize()
self.crane = Crane()
pyxel.run(self.update, self.draw) #こうすれば Appクラスのupdate, drawをpyxel.runに渡せる def update(self):
self.crane.move()
def draw(self):
pyxel.cls(7)
self.crane.draw()
self.prize.draw()
App() # Appを初期化すると __init__が実行される
オセロ(仮) 12:30~